#!/usr/bin/python

# Copyright (c) 2017, Giovanni Sciortino (@giovannisciortino)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

DOCUMENTATION = r"""
module: rhsm_repository
short_description: Manage RHSM repositories using the subscription-manager command
description:
  - Manage (Enable/Disable) RHSM repositories to the Red Hat Subscription Management entitlement platform using the C(subscription-manager)
    command.
author: Giovanni Sciortino (@giovannisciortino)
notes:
  - In order to manage RHSM repositories the system must be already registered to RHSM manually or using the Ansible M(community.general.redhat_subscription)
    module.
  - It is possible to interact with C(subscription-manager) only as root, so root permissions are required to successfully
    run this module.
requirements:
  - subscription-manager
extends_documentation_fragment:
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: full
options:
  state:
    description:
      - If state is equal to present or disabled, indicates the desired repository state.
      - In community.general 10.0.0 the states V(present) and V(absent) have been removed. Please use V(enabled) and V(disabled)
        instead.
    choices: [enabled, disabled]
    default: "enabled"
    type: str
  name:
    description:
      - The ID of repositories to enable.
      - To operate on several repositories this can accept a comma separated list or a YAML list.
    required: true
    type: list
    elements: str
  purge:
    description:
      - Disable all currently enabled repositories that are not not specified in O(name). Only set this to V(true) if passing
        in a list of repositories to the O(name) field. Using this with C(loop) is likely not to have the desired result.
    type: bool
    default: false
"""

EXAMPLES = r"""
- name: Enable a RHSM repository
  community.general.rhsm_repository:
    name: rhel-7-server-rpms

- name: Disable all RHSM repositories
  community.general.rhsm_repository:
    name: '*'
    state: disabled

- name: Enable all repositories starting with rhel-6-server
  community.general.rhsm_repository:
    name: rhel-6-server*
    state: enabled

- name: Disable all repositories except rhel-7-server-rpms
  community.general.rhsm_repository:
    name: rhel-7-server-rpms
    purge: true
"""

RETURN = r"""
repositories:
  description:
    - The list of RHSM repositories with their states.
    - When this module is used to change the repository states, this list contains the updated states after the changes.
  returned: success
  type: list
"""

import os
from fnmatch import fnmatch
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule


class Rhsm:
    def __init__(self, module):
        self.module = module
        self.rhsm_bin = self.module.get_bin_path("subscription-manager", required=True)
        self.rhsm_kwargs = {
            "environ_update": dict(LANG="C", LC_ALL="C", LC_MESSAGES="C"),
            "expand_user_and_vars": False,
            "use_unsafe_shell": False,
        }

    def run_repos(self, arguments):
        """
        Execute `subscription-manager repos` with arguments and manage common errors
        """
        rc, out, err = self.module.run_command([self.rhsm_bin, "repos"] + arguments, **self.rhsm_kwargs)

        if rc == 0 and out == "This system has no repositories available through subscriptions.\n":
            self.module.fail_json(msg="This system has no repositories available through subscriptions")
        elif rc == 1:
            self.module.fail_json(msg=f"subscription-manager failed with the following error: {err}")
        else:
            return rc, out, err

    def list_repositories(self):
        """
        Generate RHSM repository list and return a list of dict
        """
        rc, out, err = self.run_repos(["--list"])

        repo_id = ""
        repo_name = ""
        repo_url = ""
        repo_enabled = ""

        repo_result = []
        for line in out.splitlines():
            # ignore lines that are:
            # - empty
            # - "+---------[...]" -- i.e. header
            # - "    Available Repositories [...]" -- i.e. header
            if line == "" or line[0] == "+" or line[0] == " ":
                continue

            if line.startswith("Repo ID: "):
                repo_id = line[9:].lstrip()
                continue

            if line.startswith("Repo Name: "):
                repo_name = line[11:].lstrip()
                continue

            if line.startswith("Repo URL: "):
                repo_url = line[10:].lstrip()
                continue

            if line.startswith("Enabled: "):
                repo_enabled = line[9:].lstrip()

                repo = {
                    "id": repo_id,
                    "name": repo_name,
                    "url": repo_url,
                    "enabled": True if repo_enabled == "1" else False,
                }

                repo_result.append(repo)

        return repo_result


def repository_modify(module, rhsm, state, name, purge=False):
    name = set(name)
    current_repo_list = rhsm.list_repositories()
    updated_repo_list = deepcopy(current_repo_list)
    matched_existing_repo = {}
    for repoid in name:
        matched_existing_repo[repoid] = []
        for idx, repo in enumerate(current_repo_list):
            if fnmatch(repo["id"], repoid):
                matched_existing_repo[repoid].append(repo)
                # Update current_repo_list to return it as result variable
                updated_repo_list[idx]["enabled"] = True if state == "enabled" else False

    changed = False
    results = []
    diff_before = ""
    diff_after = ""
    rhsm_arguments = []

    for repoid in matched_existing_repo:
        if len(matched_existing_repo[repoid]) == 0:
            results.append(f"{repoid} is not a valid repository ID")
            module.fail_json(results=results, msg=f"{repoid} is not a valid repository ID")
        for repo in matched_existing_repo[repoid]:
            if state in ["disabled", "absent"]:
                if repo["enabled"]:
                    changed = True
                    diff_before += f"Repository '{repo['id']}' is enabled for this system\n"
                    diff_after += f"Repository '{repo['id']}' is disabled for this system\n"
                results.append(f"Repository '{repo['id']}' is disabled for this system")
                rhsm_arguments += ["--disable", repo["id"]]
            elif state in ["enabled", "present"]:
                if not repo["enabled"]:
                    changed = True
                    diff_before += f"Repository '{repo['id']}' is disabled for this system\n"
                    diff_after += f"Repository '{repo['id']}' is enabled for this system\n"
                results.append(f"Repository '{repo['id']}' is enabled for this system")
                rhsm_arguments += ["--enable", repo["id"]]

    # Disable all enabled repos on the system that are not in the task and not
    # marked as disabled by the task
    if purge:
        enabled_repo_ids = set(repo["id"] for repo in updated_repo_list if repo["enabled"])
        matched_repoids_set = set(matched_existing_repo.keys())
        difference = enabled_repo_ids.difference(matched_repoids_set)
        if len(difference) > 0:
            for repoid in difference:
                changed = True
                diff_before.join(f"Repository '{repoid}'' is enabled for this system\n")
                diff_after.join(f"Repository '{repoid}' is disabled for this system\n")
                results.append(f"Repository '{repoid}' is disabled for this system")
                rhsm_arguments.extend(["--disable", repoid])
            for updated_repo in updated_repo_list:
                if updated_repo["id"] in difference:
                    updated_repo["enabled"] = False

    diff = {
        "before": diff_before,
        "after": diff_after,
        "before_header": "RHSM repositories",
        "after_header": "RHSM repositories",
    }

    if not module.check_mode and changed:
        rc, out, err = rhsm.run_repos(rhsm_arguments)
        results = out.splitlines()
    module.exit_json(results=results, changed=changed, repositories=updated_repo_list, diff=diff)


def main():
    module = AnsibleModule(
        argument_spec=dict(
            name=dict(type="list", elements="str", required=True),
            state=dict(choices=["enabled", "disabled"], default="enabled"),
            purge=dict(type="bool", default=False),
        ),
        supports_check_mode=True,
    )

    if os.getuid() != 0:
        module.fail_json(msg="Interacting with subscription-manager requires root permissions ('become: true')")

    rhsm = Rhsm(module)

    name = module.params["name"]
    state = module.params["state"]
    purge = module.params["purge"]

    repository_modify(module, rhsm, state, name, purge)


if __name__ == "__main__":
    main()
